package org.evolAI.organism.domain;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlRootElement;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.evolAI.organism.util.RandomNum;

@Entity
@Table(name="Step")
@XmlRootElement
public class Step {

	private long id;
	private StepTypes stepType;
	private Condition condition;
	private DataTypes dataType;
	private String dataString = "";
	private Integer dataInt = 0;
    private List<String> dataList;

    public enum StepTypes
	{
		Add,
        Sub, 

		Load,
        LoadLocal,
        LoadIndirect,
        LoadPart,

        RecallRandom,
        RecallEarliest,
        RecallLatest,
        RecallStrongest,

        StoreLocal,
        StoreIndirect,
		Remember,

        Dup,
        Pop,

        ConvertUp,
        ConvertDown,

        Compare,

		JumpForward,
		JumpBackward,
		JumpNextMarker,
		JumpPrevMarker,
		Branch,
        BranchIndirect,
		Return,
		Marker,

		AddToOutput
	}

	public enum Condition {
		Equal,
		LessThan,
		LessThanOrEqual,
		GreaterThan,
		GreaterThanOrEqual,
		NotEqual;		
	}

	public enum DataTypes {
		String,
		Int,
        List
	}
	
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "step_id")
	public long getId() {
		return this.id;
	}
	
	public void setId(long id) {
		this.id = id;
	}
	
	@Enumerated(EnumType.STRING)
	@Column(name = "step_type", nullable = false)
    public StepTypes getStepType() {
		return stepType;
	}

	public void setStepType(StepTypes stepType) {
		this.stepType = stepType;
	}

	@Enumerated(EnumType.STRING)
	@Column(name="step_condition")
	public Condition getCondition() {
		return condition;
	}

	public void setCondition(Condition condition) {
		this.condition = condition;
	}

	@Transient
	public boolean isUnevaluated() {
		return condition == null;
	}
	
	@Enumerated(EnumType.STRING)
	@Column(name = "data_type")
	public DataTypes getDataType() {
		return dataType;
	}

	public void setDataType(DataTypes dataType) {
		this.dataType = dataType;
	}

	@Column(name = "data_string")
	public String getDataString() {
		return dataString;
	}

	public void setDataString(String dataString) {
		this.dataString = dataString;
	}
	
	public void setData(String dataString) {
		this.dataType = DataTypes.String;
		this.setDataString(dataString);
	}

	@Column(name = "data_int")
	public Integer getDataInt() {
		return dataInt;
	}

	public void setDataInt(int dataInt) {
		this.dataInt = dataInt;
	}

	public void setData(int dataInt) {
		this.dataType = DataTypes.Int;
		this.setDataInt(dataInt);
	}

	@ElementCollection
	public List<String> getDataList() {
		return dataList;
	}

	public void setDataList(List<String> dataList) {
		this.dataList = dataList;
	}

	public void setData(List<String> dataList) {
		this.dataType = DataTypes.List;
		this.setDataList(dataList);
	}
	
	@Override
	public boolean equals(Object obj) {
		return EqualsBuilder.reflectionEquals(this, obj);
	}
	  
	@Override
	public int hashCode() {
		return HashCodeBuilder.reflectionHashCode(this);
	}
	  
	@Override
	public String toString() {
		return ToStringBuilder.reflectionToString(this);
	}
	
	protected Step() { }
	
	public Step(StepTypes stepType) {
		this.stepType = stepType;
	}
	
	protected Step(Step step) {
        this.stepType = step.stepType;
        this.condition = step.condition;
        this.dataType = step.dataType;
        this.dataString = step.dataString;
        this.dataInt = step.dataInt;
        if (step.dataList != null) {
        	this.dataList = new ArrayList<String>();
        	for (String s: step.dataList) {
        		this.dataList.add(s);
        	}
        }
	}
	
    public Step copy() {
        return new Step(this);
    }

    public void changeRandom(Organism org) {
        for (int i = 0; i < 3; i++) {
            int chance = RandomNum.getUniform(3);
            if (chance == 1) {
                switch (i) {
                    case 0:
                        //change steptype
                        randomizeType(org);
                        break;
                    case 1:
                        //change condition
                        randomizeCondition();
                        break;
                    case 2:
                        //change datatype and value or just value
                        int rndType = RandomNum.getUniform(2);
                        if (rndType == 1)
                        {
                            randomizeDataType();
                        }
                        randomizeData(org);
                        break;
                }
            }
        }
    }

    private void randomizeType(Organism org) {
    	
        int rndStep = RandomNum.getUniform(27);
        switch (rndStep) {
            case 0:
                stepType = StepTypes.Add;
                break;
            case 1:
                stepType = StepTypes.Sub;
                dataType = DataTypes.Int;
                break;
            case 2:
                stepType = StepTypes.Load;
                break;
            case 3:
                stepType = StepTypes.LoadLocal;
                dataInt = RandomNum.getExponential(10, 1);
                break;
            case 4:
                stepType = StepTypes.LoadIndirect;
                break;
            case 5:
                stepType = StepTypes.LoadPart;
                break;
            case 6:
                stepType = StepTypes.RecallRandom;
                break;
            case 7:
                stepType = StepTypes.RecallEarliest;
                break;
            case 8:
                stepType = StepTypes.RecallLatest;
                break;
            case 9:
                stepType = StepTypes.RecallStrongest;
                break;
            case 10:
                stepType = StepTypes.StoreLocal;
                dataInt = RandomNum.getExponential(10, 1);
                break;
            case 11:
                stepType = StepTypes.StoreIndirect;
                break;
            case 12:
                stepType = StepTypes.Remember;
                break;
            case 13:
                stepType = StepTypes.Dup;
                break;
            case 14:
                stepType = StepTypes.Pop;
                break;
            case 15:
                stepType = StepTypes.ConvertUp;
                break;
            case 16:
                stepType = StepTypes.ConvertDown;
                break;
            case 17:
                stepType = StepTypes.Compare;
                break;
            case 18:
                stepType = StepTypes.JumpForward;
                break;
            case 19:
                stepType = StepTypes.JumpBackward;
                break;
            case 20:
                stepType = StepTypes.JumpNextMarker;
                break;
            case 21:
                stepType = StepTypes.JumpPrevMarker;
                break;
            case 22:
                stepType = StepTypes.Branch;
                dataInt = RandomNum.getUniform(org.getCodes().size());
                break;
            case 23:
                stepType = StepTypes.BranchIndirect;
                break;
            case 24:
                stepType = StepTypes.Return;
                break;
            case 25:
                stepType = StepTypes.Marker;
                break;
            case 26:
                stepType = StepTypes.AddToOutput;
                break;
        }
        randomizeDataType();
    }

    private void randomizeCondition() {
        int rndCond = RandomNum.getUniform(7);
        switch (rndCond) {
            case 0:
                condition = Condition.Equal;
                break;
            case 1:
                condition = Condition.GreaterThan;
                break;
            case 2:
                condition = Condition.GreaterThanOrEqual;
                break;
            case 3:
                condition = Condition.LessThan;
                break;
            case 4:
                condition = Condition.LessThanOrEqual;
                break;
            case 5:
                condition = Condition.NotEqual;
                break;
            case 6:
                condition = null;
                break;
        }
    }

    private void randomizeDataType() {
        int rndDataType = RandomNum.getUniform(3);
        switch (rndDataType) {
            case 0:
                dataType = DataTypes.Int;
                break;
            case 1:
                dataType = DataTypes.List;
                break;
            case 2:
                dataType = DataTypes.String;
                break;
        }
    }

    private void randomizeData(Organism org) {
        if (stepType != StepTypes.Branch && dataType != null) {
            switch (dataType) {
                case Int:
                    dataInt = RandomNum.getExponential(10, 1);
                    break;
                case String:
                    dataString = org.recallRandom();
                    break;
                case List:
                    dataList = new ArrayList<String>();
                    dataList.add(org.recallRandom());
                    break;
            }
        }
    }

    public static Step CreateRandom(Organism org)
    {
        Step newStep = new Step();

        newStep.randomizeType(org);
        newStep.randomizeCondition();
        newStep.randomizeData(org);

        return newStep;
    }
    	
}
